Programming Manual Chap. 07
The Lua Language, and Custom Features Wild Pockets uses the Lua language to control your game. It is not practical to include the Lua manual inside the Wild Pockets manual - you will need to go read the Lua manual separately 9 to learn the Lua language. However, we have a slightly customized version of Lua. Although you will not see any major differences between the Lua in Wild Pockets and traditional Lua, you will see some subtle differences. This chapter enumerates those differences. The Module System The Module System we Discarded Before we begin, we should mention that the Lua manual refers to a module system that is built-in to Lua. This module system has been stripped out, and has been replaced with one that retrieves source files from the Wild Pockets file server. The Main Module Every game has a main module - a Lua script file which is loaded when the game is launched. The saved game file contains the filename of the main module. In the development environment, open the global scene properties dialog, and look in the script properties. There, you will find the filename of the main module. You can point this at any lua script you have written. This main module will automatically be "imported" (loaded) when the game starts. Creating Script Files To create script files in the first place, open the builder. Select the menu item "Folders/Open Local Folder". This will open an explorer folder. The files in this folder are accessible to your game when running in the builder. They can also be easily mirrored to the server using the synchronization tool. Script File Naming Conventions When you put a script file into the scripts directory on your machine, it must have the extension "lua." All filenames on the Wild Pockets file server have usernames. For example, if Josh puts a file called "myGame.lua" into his local folder, then the file's full name is "Josh/myGame.lua". Wild Pockets doesn't use abbreviated pathnames: you must always use the full path in your scripts. Module Initialization It is intended that your modules should contain only function definitions and class definitions. It is possible to put other commands that are not inside a function into a module, but this may not always be supported. The first thing that happens when a module is loaded is that the function and class definitions inside the module are processed, top-to-bottom. This has the effect of building up a scope full of functions and classes. The second thing that happens is that the system looks inside the module for a definition of function _init. If present, this function is called: this is the module constructor. Finally, if the module is the main module, then the function onSceneStart is called within the module. Manually Imported Modules In addition to the one lua script file that is automatically imported at game initialization time, you can manually import additional modules using the "import" statement: import("josh/robotCode.lua") This will download the Lua script file "josh/robotCode.lua" from the Wild Pockets server, and load it. When loading multiple modules, each module exists within its own scope. By default, a module cannot see the functions and classes defined in any other module. In fact, a scope is a lua table. When you import a module, this scope table is automatically created. All the defined functions and classes are stored inside this scope table. The import function actually returns the scope table for the module it imported. Using the scope table, you can retrieve the functions, classes, and global variables from that scope. Here's an example: robotModule = import("josh/robotCode.lua") robotModule.killRobots() -- call the function 'killRobots' defined in the module Don't Put Import Statements at the Top C programmers might think it's normal to put the import statement at the top of your file, where the C preprocessor include-directives normally go. But that's not what you're supposed to do. Import-statements are executable statements, they go inside functions. Often, it makes sense to put them inside the _init function: myModule = import("rharke/mymodule.lua") -- wrong function _init() myModule = import("rharke/mymodule.lua") -- right end It is also possible to put them right inside another function, such as this one: function calculateCosts(data) local mathLibrary = import("josh/mathLibrary.lua") local results = mathLibrary.analyze(data) ... end This approach has the advantage that the file will only be imported if the calculateCosts function gets run. Duplicate Imports If you import a file twice, it will not be loaded twice. The first time, it will be loaded. The second time, the import statement will just return the same scope table that it returned the first time. You can use the import-statement to import the main module. This will return you the scope table of the main module. Of course, since the main module was already imported, this is a duplicate import, and will not trigger the main module to be loaded again. Two Modules that Import Each Other It is possible for two modules to import each other, so that each can call functions that are defined in the other. However, there's a small caveat. Normally, the import statement doesn't return until the module is loaded and the initialization function is finished. But what if module A imports module B, and then module B turns around and imports module A? In that case, B cannot wait for A to finish initializing, because A is already waiting for B to finish initializing. The deadlock is broken by having the second import statement return before the initialization function is finished. Usually, this is not an issue. The Three Scope Levels There are three scope levels in Wild Pockets: *Local variables *Global variables *Super-global variables Local variables are created when you use the local keyword inside a function. These are only visible within the one function: function xyz() local a = 3 -- this is visible only inside function xyz print(a) end Global variables are created when you assign a variable without using the local keyword. Here, the word 'global' is somewhat misleading - it is true that these variables are visible to all the functions in the module, but they're only visible to functions in the same module: function wxy() b = 3 -- this is visible to all functions in this module print(b) end The last level, super-global, is for variables that are visible throughout the system. To assign a super-global, you have to use the table 'GLOBAL'. But to read one, you can just read it like any other variable: function vwy() GLOBAL"c" = 3 --- this is visible anywhere. print© end There is a precedence ordering: local variables override global variables, and global variables override super-global variables. Class Definitions Classes and Instances Wild Pockets provides a means for you to define your own classes. Classes work similarly to classes in other object-oriented languages- you can create a class template that describes a set of functionality, and then create instances of that class. Additionally, you can set the class of a SceneObject, so that the object takes on the behavior described by your class. You define a class as follows: MyClass = Class.new() To create an instance of the class, use the new operator on the class: local instance = MyClass.new() When an instance is created, a table is created to store data for the instance. You can access fields of the instance using the usual dot-notation: local instance = MyClass.new() instance.field1 = "hello" instance.field2 = "goodbye" print(instance.field1) -- prints hello print(instance.field2) -- prints goodbye Fields are not declared. You simply store data to create a field, as shown above. Instance Methods You can add instance methods to your class using this notation: function MyClass:method1(arg1, arg2, ...) print(self.field1) print(self.field2) end This method actually has three formal parameters: self, arg1, arg2. The self parameter is hidden, it is meant to hold the handle of the instance. Using the instance handle, you can access the fields of the instance, as shown above. Note the colon in the method declaration. The colon is what instructs Lua to add a hidden parameter, self, to hold the instance handle. To call this method, you need to use a colon in the method call as well: local instance = MyClass.new() instance:method1("apple", "banana") The colon in the method-call instructs lua to pass the instance handle prior to the other parameters. In other words, this method call is actually passing three actual parameters: instance, "apple", "banana". These three actual parameters correspond to the three formal parameters self, arg1, arg2. Note to C++ programmers: the self argument is directly analogous to this in C++. In C++, you can evaluate the expression this->m_instanceVariable to access an instance variable, but you can also just access the instance variable directly, using the expression m_instanceVariable. The latter syntax does not work in Lua: in lua, you have to use the instance handle explicitly. If you're in a method, the hidden parameter self usually contains the instance handle, so you would say self.instanceVar. Class Methods A class method differs from an instance method as follows: to invoke a class method, you don't need an instance. For example, the method MyClass.new is a class method. Class methods are defined as follows: function MyClass.method2(arg1, arg2, ...) etcetera... end Note the dot instead of a colon in the method declaration. The method call must also use a dot instead of a colon: MyClass.method2("hello", "goodbye") So as you can see, class methods are invoked by naming the class explicitly. Since no specific instance of the class is involved, no instance is passed to the method, and the method doesn't have a self parameter to hold an instance handle. You actually can invoke these methods using an instance handle, but it acts the same as if you had used the class name directly. This call is functionally equivalent to the one above: local instance = MyClass.new() instance.method2("hello", "goodbye") Setting the Class of a Scene Object You can associate a class with a SceneObject. If you do, the SceneObject will have all the methods of the class, and all the methods of SceneObject, combined. To set the class of an object programmatically, use setScriptClass, which takes a lua script filename and a classname: obj:setScriptClass("josh/RobotCode.lua","TankBot") The lua script filename will be imported using the module system. Then, the module will be searched for the specified classname. In the example above, the module "josh/RobotCode.lua" will be imported. This module should contain the command TankBot = Class.new() A SceneObject whose class has been set acts like a hybrid between a SceneObject and an object of the class. You can access fields on it, just as you would for any instance of the class: local obj = SceneManager.getObject("robot") obj:setScriptClass("josh/robotCode.lua", "TankBot") obj.hitPoints = 20 obj.attackStrength = 5 It also will support all methods of the class, and all methods of SceneObject, combined. If the class contains a method whose name is the same as a method of SceneObject, then the class method overrides the SceneObject method. If you do override a SceneObject method, WildPockets built-in methods never call your overriddes. Only your code is affected by your overrides. For example, you can override setPosition if you wish, but this won't alter the behavior of built-in mechanisms like the PathManager. Specifying a Class in the Builder You can also set the class of an object in the builder. Select the object, open the properties panel, and select the script properties. From there, you can edit the script module filename and script classname. When the game starts, the SceneObject will automatically be made into an instance of the class. Specifying a Class in a Model Exporter It is possible to specify the module and class of a SceneObject in the model exporter. In this case, when you call SceneManager.createObject, the SceneObject will automatically be made into an instance of the class. The Constructor If you wish, you can add an instance method _init to the class. This method is called 'the constructor.' This is called whenever the instance comes into existence. There is a secondary constructor called onStart which is only called when a SceneObject comes into existence with a class already assigned. To clarify, here is a table of all the possible ways an instance can come into existence: In addition, there is an optional onDelete method which is called if a SceneObject with a class is deleted. Passing Instance-Methods to the Timer You may be familiar with the Wild Pockets Timer functionality, which lets you set up code to run after a delay. Passing instance-methods to the timer is a little tricky. This won't work: Timer.later(object1:method1, 10) --- doesn't work. This seems intuitively correct, it would seem that you're asking the timer to invoke the method object1:method1 10 seconds in the future. But it doesn't work. Remember, a method is really just a function with a hidden parameter, self. When you call a method, you're really just calling a function and passing it an extra, hidden parameter. So we need the timer to call method1, and we also need it to pass object1 as a hidden parameter. That's too much to ask, though: the timer will call method1 if we ask it to, but it won't pass in object1 for us: the timer doesn't have a mechanism to pass parameters at our request. Here's the solution: Timer.later(function () object1:method1() end, 10) --- This works Here, we've created a tiny function whose sole behavior is to invoke object1:method1(). We've passed that function to the timer. The Instance-Local Timer Classes on SceneObjects call for some extensions to the way timers work. For example, it is frequently desirable to have a function that is called periodically while an object exists, but to have that timer automatically be terminated when the object is destroyed. This is achieved by providing you two sets of timer functionality: *'Timer.later() and Timer.every()' create a global timer that will persist beyond the lifespan of your SceneObject. *'self:later() and self:every()' create a timer that is tied to your SceneObject, and will automatically be cleaned up. So for example: -- doMethod1 is called every 10 seconds, forever Timer.every(function() self:doMethod1() end, 10) -- doMethod2 is called every 10 seconds, until object destroyed self:every(function() self:doMethod2() end, 10) Handling Mouse Clicks A SceneObject that has a class can have click-handling methods: onClick, onLeftClick, onMiddleClick, and onRightClick. If these methods exist, they will get invoked whenever you click on the SceneObject with the class. These methods are invoked by an EventMap which is at the bottom of the EventHandler stack. Therefore, if you put any other mouse-click-catching handler on the EventHandler stack, it will override these methods. Built-in and User-definable Methods of Classes *'new' - The one built-in method: it instanciates a new instance of the class. This is called automatically when you call setScriptClass on a SceneObject - the resultant instance is then tied to that SceneObject. *'_init' - To be implemented by the user: this is called on a new instance when that instance is created: this can happen when an existing SceneObject is assigned to the class or a SceneObject of the class is instanciated, or just from manually using YourClassName.new() *_del - To be implemented by the user: this is called on an instance when that instance goes away: this can happen when a SceneObject of the class is destroyed or has its class changed, or when an instance of the class is just manually deleted. User-definable Methods of Instances of Classes attached to SceneObjects (events) *'onStart' - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being instanciated. (Note that this is therefore only called when the SceneObject already has a class assigned to it at creation time) *'onDelete' - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being deleted. *'onClick' - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on (regardless of mouse button: this will be called in addition to any of the following functions). *'onLeftClick' - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on with the left mouse button. *'onMiddleClick' - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on with the middle mouse button. *'onRightClick' - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on with the right mouse button. Built-in Methods of Instances of Classes *'every' - Analogous to Timer.every, this will call a function every so often, but will stop when the instance is deleted. *'later' - Analogous to Timer.later, this will call a function once, later, unless the instance is deleted beforehand. *'sleep' - Analogous to Timer.sleep, this will sleep for some amount of time, but will stop early if the instance is deleted. New Classes and Functions Additions to Standard Lua We have added a lot of useful classes to the stock Lua interpreter. The following is just a summary - the actual documentation for these functions, classes, and methods can be found in the API reference, which can be found inside the development environment. Improvements to Math, String, and Table libraries Standard lua includes built-in functions in the math, string, and table namespaces. We have substantially extended these function catalogs. For example, the math library includes new trigonometric functions that accept degrees instead of radians. The string library includes a large number of new functions, mostly inspired by php and python. The table library includes new functions such as array concatenation, key sorting, deep printing, and so forth. These additions are straightforward, but useful. 3D Math We have added classes designed to help record positional and rotational data. These include: *'Vector' is used for representing both Cartesian coordinates and directional vectors. It consists of three numbers usually called (X,Y,Z). It is also sometimes used to represent scaling factors. *'Rotation' is used to record orientations and rotations. Since there are so many different ways to express the idea of rotation, there are a large number of different constructors for class Rotation. *'Transform' is a small class with room to store one position-vector, one rotation, and one scale-vector. Together, these three things completely describe the location of an object. Finally, there is a class Matrix which we ask that you not use - basically, we feel that if you need to use math which is that complex, then we're doing something wrong. However, we've included it just in case somebody needs it for something we didn't expect. Coroutine Handling Lua has a built-in class coroutine which does not include any sort of scheduling support. We have replaced this class with our own version, Coroutines, which is integrated with the system timer module. The Module System We have added a module system that allows you to load script files from the Wild Pockets file server. This is described in a previous chapter. Class Definitions We have added a module system that allows you to define your own classes. If you wish, you can still use Lua metamethods to create your own class system. Abstract Data Types Most modern programming languages provide a full library of abstract data types like hash tables, binary trees, stacks, queues, priority queues, and the like. Lua is one of the few languages in which the library is missing these fundamental structures (only hash tables and stacks are provided). This is one of the larger flaws in Lua. We plan to remedy this by supplementing Lua with these core types. Currently, we have only implemented one: the double-ended queue (Deque). This can efficiently mimic a stack or a queue as well. Things we Removed We have removed a few things from the Lua interpreter, mostly for safety. These include: *The io module is gone. A web-game shouldn't be able to delete your files. *The lua module system is gone, replaced with one that downloads modules from the Wild Pockets file server.